package top.lishuoboy.redis.service;

import cn.hutool.core.date.LocalDateTimeUtil;

import javax.validation.constraints.NotBlank;
import java.time.Duration;
import java.time.LocalDateTime;

/**
 * <h1>分布式唯一id生成器</h1>
 * 有序递增、高性能
 *
 * @author lishuoboy
 * @date 2024-9-24
 */
public interface IdGenerator {
    /**
     * <h1>生成id</h1>
     * 优点: long占用存储空间小，数据库索引性能高<br>
     * 缺点：不便于阅读<br>
     *
     * @param key key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     *
     * @return id
     */
    Long genId(String key);

    /**
     * <h1>生成id</h1>
     * 缺点: String占用存储空间大，数据库索引性能低<br>
     * 优点：便于阅读<br>
     *
     * @param key         key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     * @param pre         id前缀，建议用表名缩写，user_info表用“UI”，默认为空
     * @param dateFormat  id日期格式，默认yyyyMMdd
     * @param sequenceLen id序列号长度，默认8
     *
     * @return id（pre + 格式为dateFormat的日期 + 长度为sequenceLen的数字）
     */
    String genIdStr(@NotBlank String key, String pre, DateFormatEnum dateFormat, Integer sequenceLen);


    /**
     * @param key         key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     * @param pre         id前缀，建议用表名缩写，user_info表用“UI”，默认为空
     * @param sequenceLen id序列号长度，默认8
     *
     * @return id（pre + 格式为dateFormat的日期 + 长度为sequenceLen的数字）
     */
    String genIdStr(@NotBlank String key, String pre, Integer sequenceLen);

    /**
     * @param key key建议用表名.字段名，如“user_info.id”; 默认为“table_name.id”
     * @param pre id前缀，建议用表名缩写，user_info表用“UI”，默认为空
     *
     * @return id（pre + 格式为dateFormat的日期 + 长度为sequenceLen的数字）
     */
    String genIdStr(@NotBlank String key, String pre);

    /** 日期格式 */
    enum DateFormatEnum {
        yyyyMMddHHmmss,
        yyyyMMddHHmm,
        yyyyMMddHH,
        /** 默认 */
        yyyyMMdd,
        yyyyMM,
        yyyy
    }

    /** 计算key到期时间 */
    default Duration calcExpireTime(DateFormatEnum dateFormat) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime next;
        switch (dateFormat) {
            case yyyyMMddHHmmss:
                next = now.plusSeconds(1);
                break;
            case yyyyMMddHHmm:
                next = now.plusMinutes(1).withSecond(0);
                break;
            case yyyyMMddHH:
                next = now.plusHours(1).withSecond(0).withMinute(0);
                break;
            case yyyyMMdd:
                next = now.plusDays(1).withSecond(0).withMinute(0).withHour(0);
                break;
            case yyyyMM:
                next = now.plusMonths(1).withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1);
                break;
            case yyyy:
                next = now.plusYears(1).withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1).withMonth(1);
                break;
            default:
                next = now.plusYears(1).withSecond(0).withMinute(0).withHour(0).withDayOfMonth(1).withMonth(1);
        }
        return LocalDateTimeUtil.between(now, next);
    }

}
